package com.yunhe.webmvc.servlet;

import com.alibaba.fastjson.JSON;
import com.yunhe.webmvc.annocation.RequestBean;
import com.yunhe.webmvc.annocation.RequestMapping;
import com.yunhe.webmvc.annocation.ResponseJSON;
import com.yunhe.webmvc.mode.ActionModel;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * 1.创建一个Servlet 拦截所有请求
 2. Servlet初始化时，将所有Controller 中带有@RequestMapping  注解的方法，全部放入资源池（map,key为该方法的标注路径）
 扫描Controller所在的包，将所有标注 @RequestMapping 注解的方法，转化为可访问的资源，放入资源池
 3.当请求进入该Servlet时，获取其请求路径，与资源池中的资源 key 进行匹配，匹配成功则执行其对应的 方法
 * @author LeiPeifeng
 * @version V1.0
 * @date 2017/10/12 0012 15:59
 * @Description:
 */

public class ResourceServlet extends HttpServlet{
    private static Logger log = LoggerFactory.getLogger(ResourceServlet.class);  //日志记录
    private static Map<String,ActionModel> actionpoor=new HashMap<> ();//资源池

    /**
     * 1.扫描Controller 下的所有类的所有带@RequestMapping 的方法
     * 2.初始化资源池
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        try {
            log.info("开始加载action");
            String actionPage="com.yunhe.sms.controller";//针对controller 文件的操作
            String path = actionPage.replace ( ".", "/" );
            String Classpath = Thread.currentThread ().getContextClassLoader ().getResource ( "" ).getPath ();//获取当前线程的路径
            scanAction ( Classpath+path );
        } catch (Exception e) {
            log.error("Action加载异常");
        }
        log.info("加载action完成");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding ( "UTF-8" );
        String requestURI = req.getRequestURI (); //获取请求路径
        ActionModel actionModel = actionpoor.get ( requestURI );//通过key值获取value
        if (actionModel!=null){ //通过value判断资源池是否能含有该路径
            Method method = actionModel.getMethod ();//获取方法对象
            boolean isAjax = ("XMLHttpRequest".equals(req.getHeader("X-Requested-with"))) ? true : false;//判断ajkx是否存在
            boolean isJSON = method.isAnnotationPresent ( ResponseJSON.class );//判断方法返回值类型是否是JSON
            try {
                Parameter[] parameters = method.getParameters ();//获取该方法形参列表（需要添加编译参数 -parameters）
                Object[] realparamet = new Object[parameters.length];// //实参数组
                for (int i=0;i<parameters.length;i++){
                    Parameter parameter = parameters[i];//获取每一个形参
                    String name = parameter.getName ();//获取形参名称
                    Class<?> parameTypeClass = parameter.getType ();//获取形参类型的Class对象
                    if (parameTypeClass == HttpServletRequest.class ){
                       realparamet[i] = req;//赋值给实参
                    }else if (parameTypeClass == HttpServletResponse.class ){
                        realparamet[i]=resp;//赋值给实参
                    }else if (parameter.isAnnotationPresent ( RequestBean.class )){ //如果参数是一个实体，则将请求参数注入给它的对象
                        Object obj = parameTypeClass.newInstance ();//创建实体对象
                        Map<String, String[]> parameterMap = req.getParameterMap ();// 请求参数map
                        BeanUtils.populate ( obj,parameterMap );//  //依赖jar  (commons-beanutils, commons-logging),用map填充对象
                        realparamet[i]=obj;
                    }else{//普通参数注入值
                        String parameter1 = req.getParameter ( name );////从request中获取 形参 所需的值
                        realparamet[i] = convert ( parameTypeClass,parameter1 );//调用转换方法
                    }
                }
                Object obj = method.invoke ( actionModel.getaction (),realparamet );//invoke反射中，执行这个方法，传入方法对象和该方法所需参数
                if (obj instanceof String && !isJSON){//判断是否为String类型
                    resp.setContentType ( "text/html" );
                    String path=(String)obj;
                    if (path.startsWith ( "redirect:" )){//重定向
                        resp.sendRedirect ( req.getContextPath ()+path.substring ( 9 ));
                    }else if(path.startsWith ( "forword:" )){ //转发到服务器资源
                        req.getRequestDispatcher ( req.getContextPath ()+path.substring ( 8 ) ).forward ( req,resp );
                    }else{ //转发访问jsp
                        req.getRequestDispatcher ( req.getContextPath ()+"/WEB-INF/view/"+path+".jsp" ).forward ( req,resp );
                    }
                }else if (isJSON){ //如果返回值weiJSON 则返回JSON
                    resp.setContentType ( "application/json" );
                    writeJSON(obj,resp);
                }else{
                    resp.sendError ( 404,"返回值异常");
                }
            } catch (Exception e) {
                e.printStackTrace ();
                if (isAjax){
                    HashMap<String, Object> map = new HashMap<> ();
                    map.put ( "msg","出错了！" );
                    writeJSON ( map,resp );
                }else{
                    resp.sendError ( 500 );
                }
            }
        }else{
            resp.sendError ( 404);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet ( req,resp );
    }

    /**
     * 递归遍历，找出目标文件下的所有文件的路径
     * 把符合要求的资源加入资源池中
     * @param path
     */
    public static void scanAction(String path) throws Exception {
        File file = new File ( path );//对指定路径的文件的操作
        if (file.isDirectory ()){//判断是否含有文件夹
            File[] files = file.listFiles (); //找出所有的文件夹
            for (File f : files){
                String path1 = f.getPath ();//获取文件夹的路径
                scanAction ( path1 );//
            }
        }else{
            String className = file.getPath ().substring ( path.indexOf ( "com" ), path.lastIndexOf ( "." ) ).replace ( File.separator, "." );
            Class<?> aClass = Class.forName ( className );//通过反射，获取类的对象
            if (aClass.isAnnotationPresent ( RequestMapping.class )){
                Object action = aClass.newInstance (); //创建当前Action的对象
                RequestMapping classAnno = aClass.getAnnotation ( RequestMapping.class );//获取当前Action注解
                String classPath = classAnno.value ();//拿到Controller类的标识路径
                Method[] methods = aClass.getMethods ();//获取类中所有的方法对象
                for (Method method:methods) {
                    if (method.isAnnotationPresent ( RequestMapping.class )) {
                        RequestMapping methodAnno = method.getAnnotation ( RequestMapping.class ); //获取当前方法的注解
                        String methodPath = methodAnno.value ();//拿到方法的标识路径
                        String paths=( classPath+methodPath );//访问路径
                        if (!actionpoor.containsKey (paths)){//判断资源中是否存在该路径
                            actionpoor.put (paths,new ActionModel ( method,action ));//把方法和路径存入map中
                            log.info("已加载资源：{}",paths);
                        }else {
                            log.info("存在多个相同映射：{}",paths);
                            throw new Exception("资源重复加载");
                        }
                    }
                }
            }
        }
    }

    /**
     * 写入JSON
     * @param obj
     * @param resp
     * @throws IOException
     */
    public static void writeJSON(Object obj, HttpServletResponse resp) throws IOException {
        resp.setContentType ( "text/json" );
        String string = JSON.toJSONString ( obj );
        PrintWriter writer = resp.getWriter ();
        writer.write ( string );
        writer.close ();
    }

    /**
     * 转换参数类型
     * @param parameTypeClass
     * @param parameter1
     * @return
     */
    public static Object convert(Class parameTypeClass,String parameter1 ){
        Object parameter=null;
        if (null!=parameter1 && !parameter1.isEmpty ()) {
            if (parameTypeClass == Integer.class || parameTypeClass == int.class) {
                parameter = Integer.valueOf ( parameter1 );
            } else if (parameTypeClass == Byte.class || parameTypeClass == byte.class) {
                parameter = Byte.valueOf ( parameter1 );
            } else if (parameTypeClass == Short.class || parameTypeClass == short.class) {
                parameter = Short.valueOf ( parameter1 );
            } else if (parameTypeClass == Long.class || parameTypeClass == long.class) {
                parameter = Long.valueOf ( parameter1 );
            } else if (parameTypeClass == Double.class || parameTypeClass == double.class) {
                parameter = Double.valueOf ( parameter1 );
            } else if (parameTypeClass == Float.class || parameTypeClass == float.class) {
                parameter = Float.valueOf ( parameter1 );
            } else if (parameTypeClass == Character.class || parameTypeClass == char.class) {
                parameter = parameter1.charAt ( 0 );
            } else if (parameTypeClass == Boolean.class || parameTypeClass == boolean.class) {
                parameter = Integer.valueOf ( parameter1 );
            } else {
                parameter = parameter1;
            }
        }
        return parameter ;
    }
}
